`timescale 1ns/100ps

`include "sim_glb.sv"

module tc;

localparam          U_DLY                   = 0;
localparam          CLK_PRD                 = 5;

localparam          PORT_NUM                = 8;
localparam          SCH_NUM                 = 20000;
localparam          ID_BW                   = $clog2(PORT_NUM);

reg                                         rst_n;
reg                                         clk;

reg                 [PORT_NUM-1:0]          req;
wire                                        req_exist;

wire                                        sch_en;
wire                [PORT_NUM-1:0]          gnt;
wire                [PORT_NUM-1:0]          gnt_hld;
wire                [ID_BW-1:0]             gnt_id;
reg                                         sch_done;
reg                 [PORT_NUM-1:0]          gnt_dly;
reg                                         busy;

initial begin:CRG
    rst_n=1'b0;
    clk=1'b0;

    rst_n=#100.5 1'b1;
    forever clk=#CLK_PRD ~clk;
end

RGRS_MNG    rgrs;
initial begin:REGRESS
    rgrs = new("tc_arb_rr", 1);

    rgrs.wait_chks_done(100_000_000);
end

initial begin:GEN_REQ
    integer         i;
    integer         cnt_req;
    reg [7:0]       rand_dat;

    req = 0;
    i   = 0;
    cnt_req = 0;

    @(posedge rst_n);

    while(cnt_req<SCH_NUM || req>0) begin
        rand_dat = $urandom_range(1, 20);
        repeat(rand_dat) begin
            @(posedge clk);

            if (sch_en==1'b1) begin
                req = #U_DLY req & (~gnt);
            end
        end
        #U_DLY;

        if (cnt_req<SCH_NUM) begin
            rand_dat = $urandom_range(0, PORT_NUM-1);
            req[rand_dat] = 1'b1;
            cnt_req = cnt_req +1;
        end
    end
    rgrs.one_chk_done("sch num is done.");
end

initial begin:GEN_BUSY
    integer         i;
    reg [7:0]       rand_dat;

    busy = 1;

    @(posedge rst_n);
    @(posedge clk);

    forever begin
        rand_dat = $urandom_range(0, 20);
        repeat(rand_dat) begin
            @(posedge clk);
        end
        #U_DLY;

        busy = 1'b0;
        @(posedge clk);
        #U_DLY;

        busy = 1'b1;
    end
end

assign sch_en = req_exist & (~busy);
arb_rr #(
        .PORT_NUM                       (PORT_NUM                       )
) u_arb_rr ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),

        .req                            (req                            ),
        .req_exist                      (req_exist                      ),

        .sch_en                         (sch_en                         ),
        .gnt                            (gnt                            ),
        .gnt_hld                        (gnt_hld                        )
);

onehot2bin #(
        .OH_BW                          (PORT_NUM                       ) 	// bit width of one-hot number
) u_oh2bin ( 
        .oh                             (gnt                            ),	// one-hot number or all zero
        .bin                            (gnt_id                         )	// binary number
);

initial begin:CHK_GNT
    integer         i;
    integer         j;

    @(posedge rst_n);
    
    forever begin
        @(posedge clk);
        while(sch_en==1'b0) begin
            @(posedge clk);
        end

        if ($onehot(gnt)==1'b0) begin
            $display("Error:%m gnt %b is not one-hot after sch_en", gnt);
            $stop;
        end

        if (sch_done==1'b1) begin
            i = (log2_f(gnt_hld)+1)%PORT_NUM;
            j = gnt_id;
//            j = log2_f(gnt);
            while(i!=j) begin
                if (req[i]!=1'b0) begin
                    $display("Error:%m gnt %b is not RR, req[%0d]!=1'b0", gnt, i);
                    $stop;
                end

                i = (i+1)%PORT_NUM;
            end

            if (req[i]!=1'b1) begin
                $display("Error:%m gnt %b is not RR, req[%0d]!=1'b1", gnt, i);
                $stop;
            end
        end
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        sch_done <= 1'b0;
        gnt_dly  <= 0;
    end else begin
        if (sch_en==1'b1) begin
            sch_done <= 1'b1;
            gnt_dly  <= gnt;
        end
        else
            ;
    end
end

always@(posedge clk) begin
    if ((sch_done==1'b1) && (gnt_dly!=gnt_hld)) begin
        $display("Error:%m gnt_dly doesn't equal to gnt_hld");
        $stop;
    end
end

function integer log2_f;
    input   integer     n;

    integer             t;
    begin
        t = 1;

        while ((1<<t)<n) begin
            t = t +1;
        end

        if (n<=1)
            log2_f = 0;
        else if (n<=2)
            log2_f = 1;
        else if (n<=4)
            log2_f = 2;
        else if (n<=8)
            log2_f = 3;
        else if (n<=16)
            log2_f = 4;
        else if (n<=32)
            log2_f = 5;
        else if (n<=64)
            log2_f = 6;
        else if (n<=128)
            log2_f = 7;
        else if (n<=256)
            log2_f = 8;
        else if (n<=512)
            log2_f = 9;
        else if (n<=1024)
            log2_f = 10;
        else if (n<=2048)
            log2_f = 11;
        else if (n<=4096)
            log2_f = 12;
        else
            log2_f = t;
    end
endfunction

endmodule
